BOLT 2 - channel management protocol

2022-04-10 ยท 14 min read

Source: https://github.com/lightning/bolts/blob/master/02-peer-protocol.md

This BOLT describes the bilateral messaging protocol for (1) establishing, (2) operating, and (3) closing lightning payment channels between two LN peers.

def: channel_id: [u8; 32] #

Channels are identified by a channel_id, which is derived from the funding tx id and index.

Before the channel is funded, the channel_id is random.

Pre-funded channels are not yet uniquely identified. Use (src_node_id, dest_node_id, channel_id) for identification.

Parties can also offer "short channel id" (SCID) aliases (size = u64), which we can use instead of the actual channel_id to avoid leaking the on-chain funding UTXO.

Channel Establishment #

Channels may be established after a secure connection is authenticated and features negotiated.

The establishment process goes through 3 phases: (1-2) negotiate the channel parameters, (3-4) create the funding tx, (5-6) lock in the channel once the funding tx is confirmed on-chain.

 funder                                 fundee
+-------+                              +-------+
|       |--(1)---  open_channel  ----->|       |  <
|       |<-(2)--  accept_channel  -----|       |  <
|       |                              |       |  <  temporary channel id
|   A   |--(3)--  funding_created  --->|   B   |  <
|       |<-(4)--  funding_signed  -----|       |  <
|       |                              |       | ---
|       |--(5)--- funding_locked  ---->|       |  <
|       |<-(6)--- funding_locked  -----|       |  <  real channel id
+-------+                              +-------+  <

def: open_channel wire message #

The channel funder sends an open_channel message to the channel fundee. Here, funder and fundee are negotiating the channel parameters for their new channel contract. A few of the important channel parameters:

  1. the channel size (amount to be funded or max liquidity)
  2. optional initial payment to fundee
  3. channel fee rate
  4. supported channel features
  5. bounds: minimum HTLC size, dust limits, max num. HTLCs, max in-flight value
  6. on-chain confirmations-to-finality

The channel id is just a random nonce at this point.

See: msgs::OpenChannel

/// An open_channel message to be sent or received from a peer
pub struct OpenChannel {
	/// The genesis hash of the blockchain where the channel is to be opened
	pub chain_hash: BlockHash,
	/// A temporary channel ID, until the funding outpoint is announced
	pub temporary_channel_id: [u8; 32],
	/// The channel value
	pub funding_satoshis: u64,
	/// The amount to push to the counterparty as part of the open, in
	/// milli-satoshi
	pub push_msat: u64,
	/// The threshold below which outputs on transactions broadcast by
	/// sender will be omitted
	pub dust_limit_satoshis: u64,
	/// The maximum inbound HTLC value in flight towards sender, in
	/// milli-satoshi
	pub max_htlc_value_in_flight_msat: u64,
	/// The minimum value unencumbered by HTLCs for the counterparty
	/// to keep in the channel
	pub channel_reserve_satoshis: u64,
	/// The minimum HTLC size incoming to sender, in milli-satoshi
	pub htlc_minimum_msat: u64,
	/// The feerate per 1000-weight of sender generated transactions,
	/// until updated by update_fee
	pub feerate_per_kw: u32,
	/// The number of blocks which the counterparty will have to wait
	/// to claim on-chain funds if they broadcast a commitment transaction
	pub to_self_delay: u16,
	/// The maximum number of inbound HTLCs towards sender
	pub max_accepted_htlcs: u16,
	/// The sender's key controlling the funding transaction
	pub funding_pubkey: PublicKey,
	/// Used to derive a revocation key for transactions broadcast
	/// by counterparty
	pub revocation_basepoint: PublicKey,
	/// A payment key to sender for transactions broadcast by counterparty
	pub payment_point: PublicKey,
	/// Used to derive a payment key to sender for transactions
	/// broadcast by sender
	pub delayed_payment_basepoint: PublicKey,
	/// Used to derive an HTLC payment key to sender
	pub htlc_basepoint: PublicKey,
	/// The first to-be-broadcast-by-sender transaction's per
	/// commitment point
	pub first_per_commitment_point: PublicKey,
	/// Channel flags
	pub channel_flags: u8,
	/// Optionally, a request to pre-set the to-sender output's
	/// scriptPubkey for when we collaboratively close
	pub shutdown_scriptpubkey: OptionalField<Script>,
	/// The channel type that this channel will represent. If none is
	/// set, we derive the channel type from the intersection of our
	/// feature bits with our counterparty's feature bits from the
	/// Init message.
	pub channel_type: Option<ChannelTypeFeatures>,
}

funder flow #

ChannelManager::create_channel

fundee flow #

ChannelManager::internal_open_channel

Channel::accept_inbound_channel

(If configured for user-level authorization) Generate Event::OpenChannelRequest

def: accept_channel wire message #

The fundee can choose to accept a new channel proposal by responding with an accept_channel message.

The channel id is still the same random nonce.

See: https://github.com/lightningdevkit/rust-lightning/blob/711bcefbcc0999543d9622c030ff7dc8118fc26f/lightning/src/ln/msgs.rs#L181

/// An accept_channel message to be sent or received from a peer
pub struct AcceptChannel {
	/// A temporary channel ID, until the funding outpoint is announced
	pub temporary_channel_id: [u8; 32],
	/// The threshold below which outputs on transactions broadcast
	/// by sender will be omitted
	pub dust_limit_satoshis: u64,
	/// The maximum inbound HTLC value in flight towards sender,
	/// in milli-satoshi
	pub max_htlc_value_in_flight_msat: u64,
	/// The minimum value unencumbered by HTLCs for the counterparty
	/// to keep in the channel
	pub channel_reserve_satoshis: u64,
	/// The minimum HTLC size incoming to sender, in milli-satoshi
	pub htlc_minimum_msat: u64,
	/// Minimum depth of the funding transaction before the channel
	/// is considered open
	pub minimum_depth: u32,
	/// The number of blocks which the counterparty will have to wait
	/// to claim on-chain funds if they broadcast a commitment transaction
	pub to_self_delay: u16,
	/// The maximum number of inbound HTLCs towards sender
	pub max_accepted_htlcs: u16,
	/// The sender's key controlling the funding transaction
	pub funding_pubkey: PublicKey,
	/// Used to derive a revocation key for transactions broadcast
	/// by counterparty
	pub revocation_basepoint: PublicKey,
	/// A payment key to sender for transactions broadcast by counterparty
	pub payment_point: PublicKey,
	/// Used to derive a payment key to sender for transactions
	/// broadcast by sender
	pub delayed_payment_basepoint: PublicKey,
	/// Used to derive an HTLC payment key to sender for transactions
	/// broadcast by counterparty
	pub htlc_basepoint: PublicKey,
	/// The first to-be-broadcast-by-sender transaction's per
	/// commitment point
	pub first_per_commitment_point: PublicKey,
	/// Optionally, a request to pre-set the to-sender output's
	/// scriptPubkey for when we collaboratively close
	pub shutdown_scriptpubkey: OptionalField<Script>,
	/// The channel type that this channel will represent. If none is
	/// set, we derive the channel type from the intersection of our
	/// feature bits with our counterparty's feature bits from the
	/// Init message.
	///
	/// This is required to match the equivalent field in
	/// [`OpenChannel::channel_type`].
	pub channel_type: Option<ChannelTypeFeatures>,
}

fundee flow #

If accept: Channel::generate_accept_channel_message

funder flow #

ChannelManager::internal_accept_channel

Channel::accept_channel

Generate Event::FundingGenerationReady

def: funding_created wire message #

The funder crafts a funding transaction, signs it, and sends it to the fundee for their signature.

The channel id is still a random nonce.

See: https://github.com/lightningdevkit/rust-lightning/blob/711bcefbcc0999543d9622c030ff7dc8118fc26f/lightning/src/ln/msgs.rs#L222

/// A funding_created message to be sent or received from a peer
pub struct FundingCreated {
	/// A temporary channel ID, until the funding is established
	pub temporary_channel_id: [u8; 32],
	/// The funding transaction ID
	pub funding_txid: Txid,
	/// The specific output index funding this channel
	pub funding_output_index: u16,
	/// The signature of the channel initiator (funder) on the
	/// initial commitment transaction
	pub signature: Signature,
}

funder flow #

ChannelManager::funding_transaction_generated

ChannelManager::funding_transaction_generated_intern

Updates the channel state and creates the FundingCreated p2p msg: Channel::get_outbound_funding_created

fundee flow #

ChannelManager::internal_funding_created

Fundee verifies, signs, returns FundingSigned msg: Channel::funding_created

Fundee finally signs here: Channel::funding_created_signature

Fundee adds channel monitor hook to watch for confirmations. Responds with FundingSigned message.

def: funding_signed wire message #

The fundee responds with their signature on the funding tx. With this signature, the funding tx is now ready for propagation by the funder.

This is the first message where we refer to the channel by its real channel id and not the random nonce.

See: https://github.com/lightningdevkit/rust-lightning/blob/711bcefbcc0999543d9622c030ff7dc8118fc26f/lightning/src/ln/msgs.rs#L235

/// A funding_signed message to be sent or received from a peer
pub struct FundingSigned {
	/// The channel ID
	pub channel_id: [u8; 32],
	/// The signature of the channel acceptor (fundee) on the
	/// initial commitment transaction
	pub signature: Signature,
}

fundee flow #

Fundee responds with FundingSigned message in tail-end of Channel::funding_created

funder flow #

Verify, hook channel monitor on tx, and broadcast funding tx: ChannelManager::internal_funding_signed

Verify, build funding tx & tx monitor: Channel::funding_signed

Broadcast funding tx: chaininterface::BroadcasterInterface::broadcast_transaction

def: funding_locked wire message #

Each node watches the chain, waiting for the funding tx to confirm. Once a node sees the funding tx confirm, they send a msgs::FundingLocked message to the other peer.

Only once both peers have seen and verified that the funding tx has confirmed, and both peers have received each other's FundingLocked message, can the channel finally enter normal operating mode.

The peers use the real channel id here.

See: https://github.com/lightningdevkit/rust-lightning/blob/711bcefbcc0999543d9622c030ff7dc8118fc26f/lightning/src/ln/msgs.rs#L244

/// A funding_locked message to be sent or received from a peer
pub struct FundingLocked {
	/// The channel ID
	pub channel_id: [u8; 32],
	/// The per-commitment point of the second commitment transaction
	pub next_per_commitment_point: PublicKey,
	/// If set, provides a short_channel_id alias for this channel.
	/// The sender will accept payments to be forwarded over this
	/// SCID and forward them to this messages' recipient.
	pub short_channel_id_alias: Option<u64>,
}

funder & fundee flow #

On funding tx confirmation #

The ChannelManager on both sides will get poked when the funding tx is sufficiently confirmed. There appear to be several different possible interfaces, depending on how the chain listening is setup, like whether we're connected to a full node or light node, or if we have some block filtering etc...

The two I can see right off the bat are the traits chain::Listen (whole new blocks) and chain::Confirm (only specific txs we're interested in, only block headers).

You choose chain::Listen if you're connected to a full node and chain::Confirm if you're connected to a light node (my speculation).

These interfaces are impl'd by ChannelManager, who will eventually call ChannelManager::do_chain_event with a callback, which can possibly generate a FundingLocked msg for sending to the other peer.

Each channel handles the tx confirm update in Channel::transactions_confirmed. Check if the tx is relevant and sufficiently confirmed, then generate the FundingLocked msg in Channel::check_get_funding_locked.

If the Channel::is_usable (both sides confirmed), then also Channel::get_announcement_sigs, which generates a msgs::AccountmentSignatures for sending, announcing the new channel.

Once the channel is_usable, we enter Normal Channel Operation.

On remote funding_locked msg rx #

Rx peer's funding_locked msg: ChannelManager::internal_funding_locked

Update the channel state w/ Channel::funding_locked.

Like before, if the channel is now usable (both sides confirmed), is configured for announcement, and is announcable, then generate, sign, and send the msg::AnnouncementSignatures.

(not clear to me yet when these are announced)

Once the channel is_usable, we enter Normal Channel Operation.

Cooperative Channel Close #

Both nodes can agree to graceful close a channel. This process consumes less fees and lets both sides access their funds immediately.

The other alternatives are unilateral close (maybe someone crashed) and revoked transaction close (the counterparty deliberately cheats and broadcasts an old state).

One side begins active shutdown of the channel and indicates how/where they want to be paid. The passive shutdown side responds similarly. Once a side has observed a shutdown, they will no longer accept new HTLCs on this channel. Once the shutdown is begun, both sides will wait for all pending HTLCs to resolve pending HTLCs to fail backwards. Once all HTLCs have resolved, both sides negotiate a final fee amount and broadcast the channel closure tx.

Protocol Diagram

+-------+                              +-------+
|       |--(1)-----  shutdown  ------->|       |
|       |<-(2)-----  shutdown  --------|       |
|       |                              |       |
|       | <complete all pending HTLCs> |       |
|   A   |                 ...          |   B   |
|       |                              |       |
|       |--(3)-- closing_signed  F1--->|       |
|       |<-(4)-- closing_signed  F2----|       |
|       |              ...             |       |
|       |--(?)-- closing_signed  Fn--->|       |
|       |<-(?)-- closing_signed  Fn----|       |
+-------+                              +-------+

def: shutdown wire message #

See: https://github.com/lightningdevkit/rust-lightning/blob/711bcefbcc0999543d9622c030ff7dc8118fc26f/lightning/src/ln/msgs.rs#L256

/// A shutdown message to be sent or received from a peer
pub struct Shutdown {
	/// The channel ID
	pub channel_id: [u8; 32],
	/// The destination of this peer's funds on closing.
	/// Must be in one of these forms: p2pkh, p2sh, p2wpkh, p2wsh.
	pub scriptpubkey: Script,
}

active closer #

ChannelManager::close_channel which immediately calls ChannelManager::close_channel_internal. chanmgr grabs the channel, moves it into shutdown mode, maybe updates monitor w/ new shutdown script, sends shutdown msg to peer. Then fails all pending HTLCs backwards: ChannelManager::fail_htlc_backwards. I guess we don't wait for HTLCs to resolve, just notify that they should fail.

Begin channel shutdown, craft Shutdown message, return failed HTLCs: Channel::get_shutdown

passive closer #

Notify the channel about the remote shutdown. Maybe update the monitor w/ shutdown script. Send the shutdown response msg. Fail any pending HTLCs. ChannelManager::internal_shutdown

Channel rx'd shutdown msg from remote peer; maybe generate a shutdown response msg: Channel::shutdown

def: closing_signed wire message #

The channel is officially shutdown and all pending HTLCs are resolved or failed. Now both peers must negotiate a fee for the closing transaction.

There are two negotiation methods: (1) the "old" method, where nodes go back and forth proposing a "fair" fee and either accepting or rejecting with their new fee, and (2) the "modern" method, where the funder proposes a permissible fee range, and the fundee chooses a value from the range.

See: https://github.com/lightningdevkit/rust-lightning/blob/711bcefbcc0999543d9622c030ff7dc8118fc26f/lightning/src/ln/msgs.rs#L279

/// A closing_signed message to be sent or received from a peer
pub struct ClosingSigned {
	/// The channel ID
	pub channel_id: [u8; 32],
	/// The proposed total fee for the closing transaction
	pub fee_satoshis: u64,
	/// A signature on the closing transaction
	pub signature: Signature,
	/// The minimum and maximum fees which the sender is willing
	/// to accept, provided only by new nodes.
	pub fee_range: Option<ClosingSignedFeeRange>,
}

/// The minimum and maximum fees which the sender is willing to place
/// on the closing transaction. This is provided in [`ClosingSigned`]
/// by both sides to indicate the fee range they are willing to use.
pub struct ClosingSignedFeeRange {
	/// The minimum absolute fee, in satoshis, which the sender is
	/// willing to place on the closing transaction.
	pub min_fee_satoshis: u64,
	/// The maximum absolute fee, in satoshis, which the sender is
	/// willing to place on the closing transaction.
	pub max_fee_satoshis: u64,
}

Normal Channel Operation #

Protocol Diagram

+-------+                               +-------+
|       |--(1)---- update_add_htlc ---->|       |
|       |--(2)---- update_add_htlc ---->|       |
|       |<-(3)---- update_add_htlc -----|       |
|       |                               |       |
|       |--(4)--- commitment_signed --->|       |
|   A   |<-(5)---- revoke_and_ack ------|   B   |
|       |                               |       |
|       |<-(6)--- commitment_signed ----|       |
|       |--(7)---- revoke_and_ack ----->|       |
|       |                               |       |
|       |--(8)--- commitment_signed --->|       |
|       |<-(9)---- revoke_and_ack ------|       |
+-------+                               +-------+

TODO(philiphayes): finish this section

def: update_add_htlc wire message #

/// An update_add_htlc message to be sent or received from a peer
pub struct UpdateAddHTLC {
	/// The channel ID
	pub channel_id: [u8; 32],
	/// The HTLC ID
	pub htlc_id: u64,
	/// The HTLC value in milli-satoshi
	pub amount_msat: u64,
	/// The payment hash, the pre-image of which controls HTLC redemption
	pub payment_hash: PaymentHash,
	/// The expiry height of the HTLC
	pub cltv_expiry: u32,
	pub(crate) onion_routing_packet: OnionPacket,
}